/**
 * XQuery to Scala Translator
 * Fatemeh Borran-Dejnabadi
 * 15 December 2004
 */
// $Id: Analyzer.scala 10847 2007-04-26 14:16:28Z michelou $

package xquery2src

/** This class implements the semantic analyzer of xquery.
 */
class Analyzer extends Printer {

  var globalScope: Scope = new Scope(null)

  /** The current scope.
   */
  var scope:Scope = globalScope

  /** flag for variables in for, let, some and every
   */
  //override var flag:boolean = true; // for, some, every
  flag = true

  var types: Array[Type] = null

  {
    // enter a predefined function into the top-level scope
    types = new Array[Type](1);
    types.update(0, Types.STRING_TYPE);
    globalScope.enter(
        new Symbol(0,
                   "doc",
                   new FunType(types, Types.COLLECTION_TYPE),
                   false));

    types = new Array[Type](1);
    types.update(0, Types.STRING_TYPE);
    globalScope.enter(
        new Symbol(0,
                   "document",
                   new FunType(types, Types.COLLECTION_TYPE),
                   false));

    types = new Array[Type](1);
    types.update(0, Types.COLLECTION_TYPE);
    globalScope.enter(
        new Symbol(0,
                   "min",
                   new FunType(types, Types.INT_TYPE),
                   false));

    types = new Array[Type](1);
    types.update(0, Types.COLLECTION_TYPE);
    globalScope.enter(
        new Symbol(0,
                   "max",
                   new FunType(types, Types.INT_TYPE),
                   false));

    types = new Array[Type](1);
    types.update(0, Types.COLLECTION_TYPE);
    globalScope.enter(
        new Symbol(0,
                   "count",
                   new FunType(types, Types.INT_TYPE),
                   false));

    types = new Array[Type](1);
    types.update(0, Types.COLLECTION_TYPE);
    globalScope.enter(
        new Symbol(0,
                   "sum",
                   new FunType(types, Types.INT_TYPE),
                   false));

    types = new Array[Type](1);
    types.update(0, Types.COLLECTION_TYPE);
    globalScope.enter(
        new Symbol(0,
                   "avg",
                   new FunType(types, Types.INT_TYPE),
                   false));

    types = new Array[Type](1);
    types.update(0, Types.INT_TYPE);
    globalScope.enter(
        new Symbol(0,
                   "not",
                   new FunType(types, Types.INT_TYPE),
                   false));

    types = new Array[Type](1);
    types.update(0, Types.COLLECTION_TYPE);
    globalScope.enter(
        new Symbol(0,
                   "empty",
                   new FunType(types, Types.INT_TYPE),
                   false));

    types = new Array[Type](1);
    types.update(0, Types.COLLECTION_TYPE);
    globalScope.enter(
        new Symbol(0,
                   "exists",
                   new FunType(types, Types.INT_TYPE),
                   false));

    types = new Array[Type](1);
    types.update(0, Types.UNIT_TYPE);
    globalScope.enter(
        new Symbol(0,
                   "position",
                   new FunType(types, Types.INT_TYPE),
                   false));

    types = new Array[Type](2);
    types.update(0, Types.STRING_TYPE);
    types.update(1, Types.STRING_TYPE);
    globalScope.enter(
        new Symbol(0,
                   "contains",
                   new FunType(types, Types.INT_TYPE),
                   false));

    types = new Array[Type](1);
    types.update(0, Types.COLLECTION_TYPE);
    globalScope.enter(
        new Symbol(0,
                   "distinct-values",
                   new FunType(types, Types.COLLECTION_TYPE),
                   false));

    types = new Array[Type](2);
    types.update(0, Types.STRING_TYPE);
    types.update(1, Types.STRING_TYPE);
    globalScope.enter(
        new Symbol(0,
                   "starts-with",
                   new FunType(types, Types.INT_TYPE),
                   false));

    types = new Array[Type](2);
    types.update(0, Types.STRING_TYPE);
    types.update(1, Types.STRING_TYPE);
    globalScope.enter(
        new Symbol(0,
                   "ends-with",
                   new FunType(types, Types.INT_TYPE),
                   false));

    types = new Array[Type](1);
    types.update(0, Types.UNIT_TYPE);
    globalScope.enter(
        new Symbol(0,
                   "text",
                   new FunType(types, Types.STRING_TYPE),
                   false));

  }

  /** Analyze the tree with a given scope
   */
  def analyze(tree:Tree, scope:Scope):Type = {
    val backup:Scope = this.scope;
    this.scope = scope;
    val treeType:Type = analyze(tree);
    this.scope = backup;
    treeType
  }

  /** Analyze the tree in the current scope
   */
  def analyze(tree:Tree):Type = tree match {
    case Bad(pos) =>
      tree.treeType = Types.BAD_TYPE;
      tree.treeType

    case XMLExpr(pos, tag, attrExpr, content) =>
      val local:Scope = new Scope(scope);
      analyze(attrExpr, local);
      analyze(content, local);
      tree.treeType = Types.NO_TYPE;
      tree.treeType

    case AttrExpr(pos, att, value) =>
      analyze(value);
      tree.treeType = Types.NO_TYPE;
      tree.treeType

    case Sequence(pos, singleExprs) =>
      val local:Scope = new Scope(scope);
      analyze(singleExprs, local);
      tree.treeType = Types.NO_TYPE;
      tree.treeType

    case Flwor(pos, types, whereExpr, orderByExprs, returnExpr) =>
      val local:Scope = new Scope(scope);
      analyze(types, local);
      if (whereExpr != null)
	analyze(whereExpr, local);
      if (orderByExprs != null)
	analyze(orderByExprs, local);
      tree.treeType = analyze(returnExpr, local);
      tree.treeType

    case For(pos, vars) =>
      flag = true;
      analyze(vars);
      tree.treeType = Types.NO_TYPE;
      tree.treeType

    case Let(pos, vars) =>
      flag = false;
      analyze(vars);
      tree.treeType = Types.NO_TYPE;
      tree.treeType

    case Some(pos, vars, satisfies) =>
      flag = true;
      val local:Scope = new Scope(scope);
      analyze(vars, local);
      analyze(satisfies, local);
      tree.treeType = Types.INT_TYPE;
      tree.treeType

    case Every(pos, vars, satisfies) =>
      flag = true;
      val local:Scope = new Scope(scope);
      analyze(vars, local);
      analyze(satisfies, local);
      tree.treeType = Types.INT_TYPE;
      tree.treeType

    case Var(pos, ident, domain) =>
      val domain_type:Type = analyze(domain);
      if (flag)
	tree.symbol = new Symbol(pos, ident, Types.ELEMENT_TYPE, true);
      else
	tree.symbol = new Symbol(pos, ident, domain_type, true);
      scope.enter(tree.symbol);
      tree.treeType =  tree.symbol.symbol_type;
      tree.treeType

    case If(pos, condition, thenIf, elseIf) =>
      checkSameType(pos, analyze(condition), Types.INT_TYPE);
      tree.treeType = analyze(thenIf).max(analyze(elseIf));
      tree.treeType

    case BinOp(pos, operation, operand1, operand2) =>
      if (operation == SLASH || operation == DSLASH) {
	val op1:Type = analyze(operand1);
	val op2:Type = analyze(operand2);
	if (op1.isSameType(Types.ELEMENT_TYPE) &&
            op2.isSameType(Types.ATTRIBUTE_TYPE)) {
              tree.treeType = Types.ELEMENT_TYPE;
	}
	else if (op2.isInstanceOf[FunType]) {
          tree.treeType = (op2.asInstanceOf[FunType]).resType;
	}
	else {
          checkSubType(pos, op1, Types.COLLECTION_TYPE);
          checkSubType(pos, op2, Types.COLLECTION_TYPE);
          tree.treeType = Types.COLLECTION_TYPE;
	}
      }
      else {
	tree.treeType = analyze(operand1).max(analyze(operand2));
      }
      tree.treeType

    case UniOp(pos, operation, operand) =>
      if (operation == SLASH || operation == DSLASH) {
	checkSubType(pos, analyze(operand), Types.COLLECTION_TYPE);
	tree.treeType = Types.COLLECTION_TYPE;
      }
      else {
	tree.treeType = analyze(operand);
      }
      tree.treeType

    case OrderBy(pos, orderBy, order) =>
      checkSubType(pos, analyze(orderBy), Types.COLLECTION_TYPE);
      tree.treeType = Types.NO_TYPE;
      tree.treeType

    case Filter(pos, primaryExpr, predicates)  =>
      analyze(predicates);
      tree.treeType = analyze(primaryExpr);      
      tree.treeType

    case IntTree(pos, value) =>
      tree.treeType = Types.INT_TYPE;
      tree.treeType

    case StringTree(pos, string)=>
      tree.treeType = Types.STRING_TYPE;
      tree.treeType

    case Element(pos, name) =>
      tree.treeType = Types.ELEMENT_TYPE;
      tree.treeType

    case Attribute(pos, name) =>
      tree.treeType = Types.ATTRIBUTE_TYPE;
      tree.treeType

    case Ident(pos, name) =>
      tree.symbol = scope.lookup(name);
      if (tree.symbol == null) {
	Report.error(pos, "not defined identificator!");
	tree.treeType = Types.BAD_TYPE;
      }
      else {
	tree.treeType = tree.symbol.symbol_type;
      }
      tree.treeType

    case Expr(pos, expr) =>
      tree.treeType = analyze(expr);
      tree.treeType

    case FunCall(pos, fname, args) =>
      val argsType:Array[Type] = analyze(args);
      tree.treeType = new FunType(argsType, Types.ANY_TYPE);
      tree.treeType

    case _ =>
      throw new Error("unknown tree!!!");
  }

  /** Analyze several trees with a given scope.
   */
  private def analyze(trees:Array[Tree], scope:Scope):Array[Type] = {
    var types:Array[Type] = new Array[Type](trees.length);
    var i:int = 0;
    while (i < trees.length) {
      types.update(i, analyze(trees.apply(i), scope));
      i = i + 1;
    }
    types
  }

  /** Analyze several trees in the current scope.
   */
  private def analyze(trees:Array[Tree]):Array[Type] = {
    return analyze(trees, scope);
  }

  /** Generates an error if the expected type is not equal
   *  to the found type.
   */
  private def checkSameType(pos:int, found:Type, expected:Type):Unit = {
    if (!found.isSameType(expected))
      error(pos, found, expected);
  }

  /** Generates an error if the expected type is not a super type
   *  to the found type.
   */
  private def checkSubType(pos:int, found:Type, expected:Type):Unit = {
    if (!found.isSubType(expected))
      error(pos, found, expected);
  }

  /** Generates an error of type
   */
  private def error(pos:int, found:Type, expected:Type):Unit = {
    Report.error(pos, "expected type: " + expected + "\n" +
                 "actual type  : " + found);
  }

}
